#include "rendPlatform.hpp"
#include "rendSystem.hpp"

#include <math.h>

namespace rend
{

const Viewport	Renderer::defaultViewport;


Viewport::Viewport()
: mColor(.5f, .5f, .5f, 1.f)
, mDepth(1.f)
, mStencil(0)
, mBufferMask(BUFFER_COLOR | BUFFER_DEPTH)
, mRect(0.f, 0.f, 1.f, 1.f)
{}


void Viewport::setRect(
	const Box2& rect)
{
	mRect = rect;
}

void Viewport::setRect(
	const vect2& minimum,
	const vect2& maximum)
{
	mRect.setMinimum(minimum);
	mRect.setMaximum(maximum);
}


const Box2& Viewport::getRect() const
{
	return mRect;
}


void Viewport::setClearColor(
	const ColorA& color)
{
	mColor = color;
}


const ColorA& Viewport::getClearColor() const
{
	return mColor;
}


void Viewport::setClearDepth(
	const float depth)
{
	mDepth = depth;
}


float Viewport::getClearDepth() const
{
	return mDepth;
}


void Viewport::setClearStencil(
	const unsigned stencil)
{
	mStencil = stencil;
}


unsigned Viewport::getClearStencil() const
{
	return mStencil;
}


void Viewport::setClearBuffers(
	const unsigned bufferMask)
{
	mBufferMask = bufferMask;
}


unsigned Viewport::getClearBuffers() const
{
	return mBufferMask;
}


void Viewport::actuate(
	const unsigned window_w,
	const unsigned window_h) const
{
	const GLint l = window_w * mRect.getMinimum().get(0);
	const GLint b = window_h * mRect.getMinimum().get(1);
	const GLsizei w = window_w * mRect.getSpan(0);
	const GLsizei h = window_h * mRect.getSpan(1);

	glViewport(l, b, w, h);

	glClearColor(mColor.getR(), mColor.getG(), mColor.getB(), mColor.getA());
	glClearDepth(mDepth);
	glClearStencil(mStencil);

	GLbitfield mask = 0;
	
	if (mBufferMask & BUFFER_COLOR)
		mask |= GL_COLOR_BUFFER_BIT;

	if (mBufferMask & BUFFER_DEPTH)
		mask |= GL_DEPTH_BUFFER_BIT;

	if (mBufferMask & BUFFER_STENCIL)
		mask |= GL_STENCIL_BUFFER_BIT;

	glScissor(l, b, w, h);
	glEnable(GL_SCISSOR_TEST);

	glClear(mask);
}


Renderer::ErrorCode Renderer::getLastError() const
{
	const ErrorCode error = mError;
	mError = ErrorCode_NoError;
	return error;
}


void Renderer::setFrameDims(
	const unsigned window_w,
	const unsigned window_h)
{
	mWindowW = window_w;
	mWindowH = window_h;
}


RenderTarget* Renderer::createRenderTarget(
	const unsigned width,
	const unsigned height,
	const PixelFormat pix,
	const ZexelFormat zex)
{
	return 0;
}


bool Renderer::deleteRenderTarget(
	RenderTarget* rtt)
{
	return false;
}


void Renderer::setViewport(
	const Viewport* vp)
{
	assert(vp);
	assert(!mContextStack.empty());

	mContextStack.top().vport = vp;
}


const Viewport* Renderer::getViewport() const
{
	assert(!mContextStack.empty());
	assert(mContextStack.top().vport);

	return mContextStack.top().vport;
}


bool Renderer::pushContext(
	const bool carry)
{
	ContextStack::value_type val;

	if (carry)	// carry over the old context
	{
		assert(!mContextStack.empty());

		ContextStack::const_reference topContext = mContextStack.top();

		val.vport	= topContext.vport;

		if (topContext.root == 0)	// context has open groups
		{
			GroupStack::const_iterator it = topContext.openStack.begin();
			for (; it != topContext.openStack.end(); ++it)
			{
				val.openStack.push_back(new Group(**it));
			}

			val.root = 0;
		}
		else						// context has no open groups
		{
			val.root = new Group(*topContext.root);
		}
	}
	else		// establish a new blank context
	{
		val.vport	= &defaultViewport;
		val.root	= 0;
	}

	mContextStack.push(val);
	return true;
}


bool Renderer::popContext()
{
	assert(mContextStack.size() > 1);

	ContextStack::reference topContext = mContextStack.top();

	assert(!topContext.root || topContext.openStack.empty());

	delete topContext.root;

	while (!topContext.openStack.empty())
	{
		delete topContext.openStack.back();
		topContext.openStack.pop_back();
	}

	mContextStack.pop();
	return true;
}


bool Renderer::openGroup(
	const Camera* camera,
	const bool ordered)
{
	assert(camera);
	assert(!mContextStack.empty());

	ContextStack::reference topContext = mContextStack.top();

	assert(topContext.root == 0);

	Group* group = new Group(camera, ordered ? 1 : 0);

	topContext.openStack.push_back(group);
	return true;
}


bool Renderer::closeGroup(
	const Translucency translucent)
{
	assert(!mContextStack.empty());

	ContextStack::reference topContext = mContextStack.top();

	assert(!topContext.openStack.empty());
	assert(topContext.root == 0);

	Group* group = topContext.openStack.back();

	topContext.openStack.pop_back();

	if (group->isEmpty())
	{
		delete group;
		return true;
	}

	if (translucent == TRANSLUCENT_TRUE)
		group->setTranslucency(true);
	else
	if (translucent == TRANSLUCENT_FALSE)
		group->setTranslucency(false);

	if (topContext.openStack.size())			// closing a nested group
		return topContext.openStack.back()->add(group);

	topContext.root = group;					// closing the root group
	return true;
}


bool Renderer::queueDraw(
	Visual*			visual,
	Material*		material,
	const matx4&	world)
{
	assert(visual);
	assert(material);
	assert(!mContextStack.empty());

	ContextStack::reference topContext = mContextStack.top();

	assert(!topContext.openStack.empty());

	return topContext.openStack.back()->add(visual, material, world);
}


bool Renderer::snapshot(
	RenderTarget* rtt)
{
	if (rtt != 0) // we don't handle non-default-RT output for now
	{
		mError = ErrorCode_InvalidRenderTarget;
		return false;
	}

	if (mWindowW == 0 || mWindowH == 0)
	{
		mError = ErrorCode_InvalidViewport;
		return false;
	}

	if (mContextStack.empty())
	{
		mError = ErrorCode_InvalidQueue;
		return false;
	}

	mContextStack.top().vport->actuate(mWindowW, mWindowH);

	if (mContextStack.top().root == 0)
		return true;
 
	mContextStack.top().root->snapshot();

	return true;
}


bool Renderer::flush(
	RenderTarget* rtt)
{
	const bool success = snapshot(rtt);

	delete mContextStack.top().root;
	mContextStack.top().root = 0;

	return success;
}


Renderer::Renderer()
: mError(ErrorCode_NoError)
, mWindowW(0)
, mWindowH(0)
{
	pushContext(false);
}


Renderer::Group::Group(const Group& source)
: mCamera(source.mCamera)
, mCentreSum(source.mCentreSum)
, mCentreNum(source.mCentreNum)
, mTranslucent(source.mTranslucent)
, mOrderCurrent(source.mOrderCurrent)
, mOrderIncrement(source.mOrderIncrement)
{
	const_iterator it = source.begin();
	for (; it != source.end(); ++it)
	{
		const Key key		= it->first;
		const Element& src	= it->second;

		assert(src.body != NULL);

		if (src.isGroup())	// deep-copy the source element
		{
			Element e;
			e.body = new Group(*reinterpret_cast< const Group* >(src.body));

			insert(value_type(key, e));
		}
		else				// duplicate the source element
		{
			insert(value_type(key, src));
		}
	}
}


Renderer::Group::~Group()
{
	for (iterator it = begin(); it != end(); ++it)
		if (it->second.isGroup())
			delete reinterpret_cast< Group* >(it->second.body);
}


void Renderer::Group::setTranslucency(const bool translucent)
{
	mTranslucent = translucent;
}


bool Renderer::Group::add(Group* group)
{
	assert(group);

	// adjust group's geometrical centre in view space
	vect3 centre;
	centre.mul(group->mCentreSum, 1.f / group->mCentreNum);

	mTranslucent = mTranslucent || group->mTranslucent; // translucency is viral
	mCentreSum.add(centre);
	++mCentreNum;

	// calculate group's sort key in the parent group
	Key key(mOrderCurrent);
	mOrderCurrent += mOrderIncrement;

	if (mOrderIncrement == 0)
	{
		// automatic sort: opaque entities come first sorted from closer to further,
		// followed by translucent entities sorted from further to closer

		key = mCamera->getFarClipDistance() + centre[2]; // we assume camera looks along -Z in view space

		if (!group->mTranslucent)
			key = -key;
	}

	Element e;
	e.body = group;

	insert(value_type(key, e));

	return true;
}


bool Renderer::Group::add(
	Visual*			visual,
	Material*		material,
	const matx4&	world)
{
	assert(visual);
	assert(material);

	// check for valid visual's bbox
	const Box3& bbox = visual->getBBox();

	if (!bbox.isValid())
		return false;

	// check if the visual's bbox is seen by the group camera
	Frustum frust;

	if (!mCamera->getFrustum(frust, world) ||
		!frust.isAABoxIntersecting(bbox.getMinimum(),
								   bbox.getMaximum()))
		return false;

	// find visual's geometrical centre in view space
	vect3 centre, centreW, centreE;

	bbox.getCentre(centre);

	world.transform3<3>(centre, centreW);
	mCamera->getInverseWorldTransform().transform3<3>(centreW, centreE);

	// adjust group's geometrical centre in view space
	const bool blending = material ? material->isBlending() : false;

	mTranslucent = mTranslucent || blending; // translucency is viral
	mCentreSum.add(centreE);
	++mCentreNum;

	// calculate visual's sort key in the parent group
	Key key(mOrderCurrent);
	mOrderCurrent += mOrderIncrement;

	if (mOrderIncrement == 0)
	{
		// automatic sort: opaque entities come first sorted from closer to further,
		// followed by translucent entities sorted from further to closer

		key = mCamera->getFarClipDistance() + centreE[2]; // we assume camera looks along -Z in view space

		if (!blending)
			key = -key;
	}

	Element e;
	e.body		= visual;
	e.material	= material;

	insert(value_type(key, e));

	return true;
}


void Renderer::Group::snapshot()
{
	for (iterator it = begin(); it != end(); ++it)
	{
		Element& e = it->second;

		if (e.isGroup())
		{
			reinterpret_cast< Group* >(e.body)->snapshot();
			continue;
		}

		// draw the visual using the supplied material
		const unsigned numPasses = e.material->getNumPasses();

		e.material->prologue();

		for (unsigned i = 0; i < numPasses; ++i)
		{
			e.material->actuate(i);
			reinterpret_cast< Visual* >(e.body)->draw();
		}

		e.material->epilogue();
	}
}

} // namespace rend
